/*
 * Copyright (C) 2010 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.googlecode.android_scripting.facade;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.List;
import java.util.concurrent.Callable;

import android.app.Service;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Contacts.PhonesColumns;
import android.telephony.CellLocation;
import android.telephony.NeighboringCellInfo;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;

import com.googlecode.android_scripting.MainThread;
import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
import com.googlecode.android_scripting.rpc.Rpc;
import com.googlecode.android_scripting.rpc.RpcParameter;
import com.googlecode.android_scripting.rpc.RpcStartEvent;
import com.googlecode.android_scripting.rpc.RpcStopEvent;

/**
 * Exposes TelephonyManager functionality.
 * 
 * @author Damon Kohler (damonkohler@gmail.com)
 * @author Felix Arends (felix.arends@gmail.com)
 */
@SuppressWarnings("deprecation")
public class PhoneFacade extends RpcReceiver {

	private final AndroidFacade mAndroidFacade;
	private final EventFacade mEventFacade;
	private final TelephonyManager mTelephonyManager;
	private final Bundle mPhoneState;
	private final Service mService;
	private PhoneStateListener mPhoneStateListener;

	public PhoneFacade(FacadeManager manager) {
		super(manager);
		mService = manager.getService();
		mTelephonyManager = (TelephonyManager) mService
				.getSystemService(Context.TELEPHONY_SERVICE);
		mAndroidFacade = manager.getReceiver(AndroidFacade.class);
		mEventFacade = manager.getReceiver(EventFacade.class);
		mPhoneState = new Bundle();
		mPhoneStateListener = MainThread.run(mService,
				new Callable<PhoneStateListener>() {
					@Override
					public PhoneStateListener call() throws Exception {
						return new PhoneStateListener() {
							@Override
							public void onCallStateChanged(int state,
									String incomingNumber) {
								mPhoneState.putString("incomingNumber",
										incomingNumber);
								switch (state) {
								case TelephonyManager.CALL_STATE_IDLE:
									mPhoneState.putString("state", "idle");
									break;
								case TelephonyManager.CALL_STATE_OFFHOOK:
									mPhoneState.putString("state", "offhook");
									break;
								case TelephonyManager.CALL_STATE_RINGING:
									mPhoneState.putString("state", "ringing");
									break;
								}
								mEventFacade.postEvent("phone",
										mPhoneState.clone());
							}
						};
					}
				});
	}

	@Override
	public void shutdown() {
		stopTrackingPhoneState();
	}

	@Rpc(description = "Starts tracking phone state.")
	@RpcStartEvent("phone")
	public void startTrackingPhoneState() {
		mTelephonyManager.listen(mPhoneStateListener,
				PhoneStateListener.LISTEN_CALL_STATE);
	}

	@Rpc(description = "Returns the current phone state and incoming number.", returns = "A Map of \"state\" and \"incomingNumber\"")
	public Bundle readPhoneState() {
		return mPhoneState;
	}

	@Rpc(description = "Stops tracking phone state.")
	@RpcStopEvent("phone")
	public void stopTrackingPhoneState() {
		mTelephonyManager.listen(mPhoneStateListener,
				PhoneStateListener.LISTEN_NONE);
	}

	@Rpc(description = "Calls a contact/phone number by URI.")
	public void phoneCall(@RpcParameter(name = "uri") final String uriString)
			throws Exception {
		Uri uri = Uri.parse(uriString);
		if (uri.getScheme().equals("content")) {
			String phoneNumberColumn = PhonesColumns.NUMBER;
			String selectWhere = null;
			if ((FacadeManager.class.cast(mManager)).getSdkLevel() >= 5) {
				Class<?> contactsContract_Data_class = Class
						.forName("android.provider.ContactsContract$Data");
				Field RAW_CONTACT_ID_field = contactsContract_Data_class
						.getField("RAW_CONTACT_ID");
				selectWhere = RAW_CONTACT_ID_field.get(null).toString() + "="
						+ uri.getLastPathSegment();
				Field CONTENT_URI_field = contactsContract_Data_class
						.getField("CONTENT_URI");
				uri = Uri.parse(CONTENT_URI_field.get(null).toString());
				Class<?> ContactsContract_CommonDataKinds_Phone_class = Class
						.forName("android.provider.ContactsContract$CommonDataKinds$Phone");
				Field NUMBER_field = ContactsContract_CommonDataKinds_Phone_class
						.getField("NUMBER");
				phoneNumberColumn = NUMBER_field.get(null).toString();
			}
			ContentResolver resolver = mService.getContentResolver();
			Cursor c = resolver.query(uri, new String[] { phoneNumberColumn },
					selectWhere, null, null);
			String number = "";
			if (c.moveToFirst()) {
				number = c
						.getString(c.getColumnIndexOrThrow(phoneNumberColumn));
			}
			c.close();
			phoneCallNumber(number);
		} else {
			mAndroidFacade.startActivity(Intent.ACTION_CALL, uriString, null,
					null, null, null, null);
		}
	}

	@Rpc(description = "Calls a phone number.")
	public void phoneCallNumber(
			@RpcParameter(name = "phone number") final String number)
			throws Exception {
		phoneCall("tel:" + URLEncoder.encode(number, "ASCII"));
	}

	@Rpc(description = "Dials a contact/phone number by URI.")
	public void phoneDial(@RpcParameter(name = "uri") final String uri)
			throws Exception {
		mAndroidFacade.startActivity(Intent.ACTION_DIAL, uri, null, null, null,
				null, null);
	}

	@Rpc(description = "Dials a phone number.")
	public void phoneDialNumber(
			@RpcParameter(name = "phone number") final String number)
			throws Exception, UnsupportedEncodingException {
		phoneDial("tel:" + URLEncoder.encode(number, "ASCII"));
	}

	@Rpc(description = "Returns the current cell location.")
	public CellLocation getCellLocation() {
		return mTelephonyManager.getCellLocation();
	}

	@Rpc(description = "Returns the numeric name (MCC+MNC) of current registered operator.")
	public String getNetworkOperator() {
		return mTelephonyManager.getNetworkOperator();
	}

	@Rpc(description = "Returns the alphabetic name of current registered operator.")
	public String getNetworkOperatorName() {
		return mTelephonyManager.getNetworkOperatorName();
	}

	@Rpc(description = "Returns a the radio technology (network type) currently in use on the device.")
	public String getNetworkType() {
		// TODO(damonkohler): API level 5 has many more types.
		switch (mTelephonyManager.getNetworkType()) {
		case TelephonyManager.NETWORK_TYPE_EDGE:
			return "edge";
		case TelephonyManager.NETWORK_TYPE_GPRS:
			return "gprs";
		case TelephonyManager.NETWORK_TYPE_UMTS:
			return "umts";
		case TelephonyManager.NETWORK_TYPE_UNKNOWN:
			return "unknown";
		default:
			return null;
		}
	}

	@Rpc(description = "Returns the device phone type.")
	public String getPhoneType() {
		// TODO(damonkohler): API level 4 includes CDMA.
		switch (mTelephonyManager.getPhoneType()) {
		case TelephonyManager.PHONE_TYPE_GSM:
			return "gsm";
		case TelephonyManager.PHONE_TYPE_NONE:
			return "none";
		default:
			return null;
		}
	}

	@Rpc(description = "Returns the ISO country code equivalent for the SIM provider's country code.")
	public String getSimCountryIso() {
		return mTelephonyManager.getSimCountryIso();
	}

	@Rpc(description = "Returns the MCC+MNC (mobile country code + mobile network code) of the provider of the SIM. 5 or 6 decimal digits.")
	public String getSimOperator() {
		return mTelephonyManager.getSimOperator();
	}

	@Rpc(description = "Returns the Service Provider Name (SPN).")
	public String getSimOperatorName() {
		return mTelephonyManager.getSimOperatorName();
	}

	@Rpc(description = "Returns the serial number of the SIM, if applicable. Return null if it is unavailable.")
	public String getSimSerialNumber() {
		return mTelephonyManager.getSimSerialNumber();
	}

	@Rpc(description = "Returns the state of the device SIM card.")
	public String getSimState() {
		switch (mTelephonyManager.getSimState()) {
		case TelephonyManager.SIM_STATE_UNKNOWN:
			return "uknown";
		case TelephonyManager.SIM_STATE_ABSENT:
			return "absent";
		case TelephonyManager.SIM_STATE_PIN_REQUIRED:
			return "pin_required";
		case TelephonyManager.SIM_STATE_PUK_REQUIRED:
			return "puk_required";
		case TelephonyManager.SIM_STATE_NETWORK_LOCKED:
			return "network_locked";
		case TelephonyManager.SIM_STATE_READY:
			return "ready";
		default:
			return null;
		}
	}

	@Rpc(description = "Returns the unique subscriber ID, for example, the IMSI for a GSM phone. Return null if it is unavailable.")
	public String getSubscriberId() {
		return mTelephonyManager.getSubscriberId();
	}

	@Rpc(description = "Retrieves the alphabetic identifier associated with the voice mail number.")
	public String getVoiceMailAlphaTag() {
		return mTelephonyManager.getVoiceMailAlphaTag();
	}

	@Rpc(description = "Returns the voice mail number. Return null if it is unavailable.")
	public String getVoiceMailNumber() {
		return mTelephonyManager.getVoiceMailNumber();
	}

	@Rpc(description = "Returns true if the device is considered roaming on the current network, for GSM purposes.")
	public Boolean checkNetworkRoaming() {
		return mTelephonyManager.isNetworkRoaming();
	}

	@Rpc(description = "Returns the unique device ID, for example, the IMEI for GSM and the MEID for CDMA phones. Return null if device ID is not available.")
	public String getDeviceId() {
		return mTelephonyManager.getDeviceId();
	}

	@Rpc(description = "Returns the software version number for the device, for example, the IMEI/SV for GSM phones. Return null if the software version is not available.")
	public String getDeviceSoftwareVersion() {
		return mTelephonyManager.getDeviceSoftwareVersion();
	}

	@Rpc(description = "Returns the phone number string for line 1, for example, the MSISDN for a GSM phone. Return null if it is unavailable.")
	public String getLine1Number() {
		return mTelephonyManager.getLine1Number();
	}

	@Rpc(description = "Returns the neighboring cell information of the device.")
	public List<NeighboringCellInfo> getNeighboringCellInfo() {
		return mTelephonyManager.getNeighboringCellInfo();
	}
}
